package com.mtoolkit.page;

import java.io.IOException;

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.TagSupport;

import com.mtoolkit.util.LogUtil;

/**
 * A TagSupport instance implementation for generate page tag in jsp.
 * It depends on <code>jsp-api.jar</code> and <code>servlet-api.jar</code> packet.
 * 
 * @author 	<a href="mailto:xishizhang@gmail.com">ZhangShixi</a>
 * @version 1.0, 20/10/2011
 * @since 	JDK1.5
 */
public class PageTag extends TagSupport {

	/** serial version id */
	private static final long serialVersionUID = -6491441137697449069L;

	private Page<?> _page;
	private String  _homePage;
	private String  _lastPage;
	private String  _prevPage;
	private String  _nextPage;
	private String  _urlAddress;
	private String  _divClass;
	private boolean _supplement;
	private String  _indexName = DEFAULT_INDEX_NAME;
	private String  _sizeName  = DEFAULT_SIZE_NAME;
	private int	    _beforeNum = DEFAULT_BEFORE_NUM;
	private int     _afterNum  = DEFAULT_AFTER_NUM;
	
	public static final int	   DEFAULT_BEFORE_NUM = 4;
	public static final int    DEFAULT_AFTER_NUM  = 4;
	public static final String DEFAULT_INDEX_NAME = "pi";
	public static final String DEFAULT_SIZE_NAME  = "ps";
	
	private static final LogUtil LOGGER = LogUtil.getLogger(PageTag.class);
	
	/**
	 * Write page tag to jsp.
	 * {@inheritDoc}
	 * 
	 * @throws JspException {@inheritDoc}
	 * @throws NullPointerException if {@code page} is null.
	 * @throws IllegalArgumentException
	 * 		   if {@code page} is not initialized or {@code urlAddress} is null or empty.
	 */
	@Override
	public int doEndTag() throws JspException {
		if (_page == null) {
			throw new NullPointerException("page");
		}
		if (!_page.isReady()) {
			throw new IllegalArgumentException("Page is not initialized!");
		}
		if (_urlAddress == null || _urlAddress.isEmpty()) {
			throw new IllegalArgumentException("urlAddress is null or empty.");
		}
		// _urlAddress should not ends with '/', if done, process it.
		if (_urlAddress.endsWith("/")) {
			_urlAddress = _urlAddress.substring(0, _urlAddress.length() - 1);
		}
		
		StringBuilder buf = new StringBuilder();
		writeStartDiv(buf);
		writePageInfo(buf);
		writeEndDiv	 (buf);
		writeToPage  (buf);
		
		LOGGER.debug("Write jsp page tag: {0}", buf.toString());
		
		return EVAL_PAGE;
	}

	// ---- publish setter methods -------------------------------------------
	/**
	 * Sets current page.
	 * 
	 * @param page current page.
	 */
	public void setPage(Page<?> page) {
		_page = page;
	}
	
	/**
	 * Sets home page name.
	 * 
	 * @param homePage home page name.
	 */
	public void setHomePage(String homePage) {
		_homePage = homePage;
	}
	
	/**
	 * Sets last page name.
	 * 
	 * @param lastPage last page name.
	 */
	public void setLastPage(String lastPage) {
		_lastPage = lastPage;
	}
	
	/**
	 * Sets previous page name.
	 * 
	 * @param prevPage previous page name.
	 */
	public void setPrevPage(String prevPage) {
		_prevPage = prevPage;
	}
	
	/**
	 * Sets next page name.
	 * 
	 * @param nextPage next page name.
	 */
	public void setNextPage(String nextPage) {
		_nextPage = nextPage;
	}
	
	/**
	 * Sets page index link address.
	 * 
	 * @param urlAddress page link address.
	 */
	public void setUrlAddress(String urlAddress) {
		_urlAddress = urlAddress;
	}
	
	/**
	 * Sets div class name.
	 * 
	 * @param divClass div class name.
	 */
	public void setDivClass(String divClass) {
		_divClass = divClass;
	}
	
	/**
	 * Sets whether or not dynamic supplement.
	 *  
	 * @param supplement {@code true} dynamic supplement; 
	 * 		  {@code false} otherwise.
	 */
	public void setSupplement(boolean supplement) {
		_supplement = supplement;
	}
	
	/**
	 * Sets page index parameter name.
	 * if not set this parameter, it will use {@link #DEFAULT_INDEX_NAME}.
	 * 
	 * @param  indexName page index parameter name.
	 * 
	 * @throws IllegalArgumentException if {@code indexName} is null or empty.
	 */
	public void setIndexName(String indexName) {
		if (indexName == null || indexName.isEmpty()) {
			throw new IllegalArgumentException("indexName is null or empty.");
		}
		_indexName = indexName;
	}
	
	/**
	 * Sets page size parameter name.
	 * if not set this parameter, it will use {@link #DEFAULT_SIZE_NAME}.
	 * 
	 * @param  sizeName page size parameter name.
	 * 
	 * @throws IllegalArgumentException if {@code sizeName} is null or empty.
	 */
	public void setSizeName(String sizeName) {
		if (sizeName == null || sizeName.isEmpty()) {
			throw new IllegalArgumentException("sizeName is null or empty.");
		}
		_sizeName = sizeName;
	}
	
	/**
	 * Sets display number before current page index.
	 * 
	 * @param  beforeNum display number before current page index.
	 * 
	 * @throws IllegalArgumentException if {@code beforeNum} is negative.
	 */
	public void setBeforeNum(int beforeNum) {
		if (beforeNum < 0) {
			throw new IllegalArgumentException("beforeNum:" + beforeNum);
		}
		_beforeNum = beforeNum;
	}
	
	/**
	 * Sets display number after current page index.
	 * 
	 * @param  afterNum display number after current page index.
	 * 
	 * @throws IllegalArgumentException if {@code afterNum} is negative.
	 */
	public void setAfterNum(int afterNum) {
		if (afterNum < 0) {
			throw new IllegalArgumentException("afterNum:" + afterNum);
		}
		_afterNum = afterNum;
	}
	
	// ---- private methods --------------------------------------------------
	private void writeStartDiv(StringBuilder buf) {
		buf.append("<div");
        if (_divClass != null && !_divClass.isEmpty()) {
            buf.append(" class=\"");
            buf.append(_divClass);
            buf.append("\"");
        }
        buf.append(">");
	}
	
	private void writeEndDiv(StringBuilder buf) {
		buf.append("</div>");
	}
	
	private void writeToPage(StringBuilder buf) throws JspException {
		try {
			pageContext.getOut().write(buf.toString());
		} catch (IOException e) {
			throw new JspException(e.getMessage(), e);
		}
	}
	
	private void writePageInfo(StringBuilder buf) {
		// count before and after number.
		int beforeCnt = countBeforeNum();
		int afterCnt  = countAfterNum();
		
		// fix before and after number if supplement is true.
		if (_supplement) {
			beforeCnt = fixBeforeCount(beforeCnt, afterCnt);
			afterCnt  = fixAfterCount(beforeCnt, afterCnt);
		}
		
		writeHomePage(buf);
		writePrevPage(buf);
		writeBeforePages(buf, beforeCnt);
		writeCurrentPage(buf);
		writeAfterPages(buf, afterCnt);
		writeNextPage(buf);
		writeLastPage(buf);
	}
	
	private int countBeforeNum() {
		return _page.getPageIndex() > _beforeNum 
	    	   ? _beforeNum : _page.getPageIndex() - 1;
	}
	
	private int countAfterNum() {
		return _page.getRemainPage() > _afterNum
			   ? _afterNum  : _page.getRemainPage();
	}
	
	private int fixBeforeCount(int beforeCnt, int afterCnt) {
		// after is not enough.
		if (afterCnt < _afterNum) {
			int befores = _page.getPageIndex() - 1;
			int needs = (_beforeNum + _afterNum) - (beforeCnt + afterCnt);
			if (befores > beforeCnt) {
				beforeCnt += befores > needs ? needs : befores; 
			}
		}
		return beforeCnt;
	}
	
	private int fixAfterCount(int beforeCnt, int afterCnt) {
		// before is not enough.
		if (beforeCnt < _beforeNum) { 
			int afters = _page.getTotalPage() - _page.getPageIndex();
			int needs = (_beforeNum + _afterNum) - (beforeCnt + afterCnt);
			if (afters > afterCnt) {
				afterCnt += afters > needs ? needs : afters; 
			}
		}
		return afterCnt;
	}
	
	private void writeHomePage(StringBuilder buf) {
		if (_homePage == null || _homePage.isEmpty()) {
			return;
		}
		
		buf.append("<label>");
		if (_page.getPageIndex() == 1) {
			buf.append(_homePage);
		} else { // pageIndex > 1
			buf.append(addHref2Index(1, _homePage));
		}
		buf.append("</label>");
	}

	private void writePrevPage(StringBuilder buf) {
		if (_prevPage == null || _prevPage.isEmpty()) {
			return;
		}
		
		buf.append("<label>");
		if (_page.getPageIndex() == 1) {
			buf.append(_prevPage);
		} else { // pageIndex > 1
			buf.append(addHref2Index(_page.getPageIndex() - 1, _prevPage));
		}
		buf.append("</label>");
	}
	
	private void writeBeforePages(StringBuilder buf, int count) {
		int currentIndex = _page.getPageIndex();
		for (int index = currentIndex - count; index < currentIndex; index++) {
			buf.append("<label>");
			buf.append(addHref2Index(index, String.valueOf(index)));
			buf.append("</label>");
		}
	}

	private void writeCurrentPage(StringBuilder buf) {
		buf.append("<label>");
		buf.append(_page.getPageIndex());
		buf.append("</label>");
	}

	private void writeAfterPages(StringBuilder buf, int count) {
		int currentIndex = _page.getPageIndex();
		for (int index = currentIndex + 1; index <= currentIndex + count; index++) {
			buf.append("<label>");
			buf.append(addHref2Index(index, String.valueOf(index)));
			buf.append("</label>");
		}
	}

	private void writeNextPage(StringBuilder buf) {
		if (_nextPage == null || _nextPage.isEmpty()) {
			return;
		}
		
		buf.append("<label>");
		if (_page.getPageIndex() >= _page.getTotalPage()) {
			buf.append(_nextPage);
		} else {
			buf.append(addHref2Index(_page.getPageIndex() + 1, _nextPage));
		}
		buf.append("</label>");
	}

	private void writeLastPage(StringBuilder buf) {
		if (_lastPage == null || _lastPage.isEmpty()) {
			return;
		}
		
		buf.append("<label>");
		if (_page.getPageIndex() >= _page.getTotalPage()) {
			buf.append(_lastPage);
		} else {
			buf.append(addHref2Index(_page.getTotalPage(), _lastPage));
		}
		buf.append("</label>");
	}
	
	private String addHref2Index(int index, String name) {
		StringBuilder buf = new StringBuilder();
		
		buf.append("<a href=\"").append(_urlAddress);
        if (_urlAddress.lastIndexOf('?') > 0) {
            buf.append("&");
        } else {
            buf.append("?");
        }
        buf.append(_indexName).append("=").append(index);
        buf.append("&");
        buf.append(_sizeName).append("=").append(_page.getPageSize());
        buf.append("\">").append(name).append("</a>");
		
		return buf.toString();
	}
	
}
